/******************************************************************************
*  Copyright(c) 2021-2023 Sietium
*
*  @file:    point_sprite.cpp
*  @brif:    point sprite
*
*  @date:    05/10/2021
*  @Author:  cary.Zhang
*
******************************************************************************/
#include <string>
#include <cmath>
#include <cstdlib>
#include <memory>
#include <vector>
#include "vmath.h"
#include "glad/gl-headers.h"

#include "glslprogram.h"
#include "texture.h"
#include "node.h"
#include "objectfactory.h"
#include "window.h"

#define NUM_STARS 3000

class Sprite: public Node
{
public:
    Sprite();
protected:
    bool startup();
    void render(double currentTime, double difTime);
    void shutdown();
protected:
    struct star_t {
        vmath::vec3 position;
        vmath::vec3 color;
    };
    void setStarData(star_t *star, int count);
private:
    GLSLProgram program;
    GLuint starTexture;
    GLuint starVao;
    GLuint starBuffer;
};

Sprite::Sprite()
{
    nodeName = "Sprite";
}

bool Sprite::startup()
{
    //glMapBufferRange added in 3.0

    if (PerfWindow::glVersion < 330) {
        printf("Failed! The GL version is less than 3.3 !\n");
        return false;
    }

    bool ok = program.build("../media/shaders/points/pointsprite.vs.glsl",
                            "../media/shaders/points/pointsprite.fs.glsl");
    if (!ok)
        return false;

    glGenVertexArrays(1, &starVao);
    glBindVertexArray(starVao);

    starTexture = Texture::loadKtxImage("../media/textures/star.ktx");

    glGenBuffers(1, &starBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, starBuffer);

    auto cb = [&](star_t *stars, int count) {
        setStarData(stars, count);
    };
    mapBufferDataCallback<star_t>(NUM_STARS, cb);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(star_t), NULL);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(star_t), (void *)sizeof(vmath::vec3));
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    return true;
}

void Sprite::render(double currentTime, double difTime)
{
    const GLfloat color[] = {0.0f, 0.0f, 0.0f, 0.0f};
    const GLfloat depth[] = {1.0f};

    glClearBufferfv(GL_COLOR, 0, color);
    glClearBufferfv(GL_DEPTH, 0, depth);

    static float angle = 0.0;
    static float step = 0.0;

    vmath::mat4 projMatrix = vmath::perspective(50.0f, getWindowRatio(), 0.1f, 3000.0f);

    program.use();
    program.setUniform1f("time", currentTime);
    program.setUniformMatrix4fv("proj_matrix", projMatrix);
    program.setUniform1f("step_size", step);
    program.setUniform1f("angle", angle);

    angle += difTime * 1.66;
    if (angle >= 360)
        angle -= 360;
    if (angle < 0)
        angle += 360;

    step += difTime * 0.006;
    if (step >= 1)
        step -= 1;
    if (step < 0)
        step += 1;

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
#ifndef USE_GLES
    /* In OpenGL, user need to enable GL_PROGRAM_POINT_SIZE before using gl_PointSize in shader.
     * In GLES, user can use gl_PointSize without calling glEnable.
     */
    glEnable(GL_PROGRAM_POINT_SIZE);
#endif

    glBindVertexArray(starVao);

    glBindTexture(GL_TEXTURE_2D, starTexture);
    glDrawArrays(GL_POINTS, 0, NUM_STARS);

#ifndef USE_GLES
    glDisable(GL_PROGRAM_POINT_SIZE);
#endif
    glDisable(GL_BLEND);
}

void Sprite::shutdown()
{
    program.clear();
#ifndef USE_GLES
    glDisable(GL_PROGRAM_POINT_SIZE);
#endif
    glDisable(GL_BLEND);
    glDeleteTextures(1, &starTexture);
    glDeleteBuffers(1, &starBuffer);
    glDeleteVertexArrays(1, &starVao);
}

void Sprite::setStarData(star_t *stars, int count)
{
    auto rand = []()->float {
        return (std::rand() % 100) * 0.01f;
    };

    for (int i = 0; i < count; i++) {
        stars[i].position[0] = (rand() * 2.0f - 1.0f) * 50.0f;
        stars[i].position[1] = (rand() * 2.0f - 1.0f) * 50.0f;
        stars[i].position[2] = rand();

        stars[i].color[0] = 0.5f + rand() * 0.5f;
        stars[i].color[1] = 0.3f + rand() * 0.5f;
        stars[i].color[2] = 0.5f + rand() * 0.5f;
    }
}

REGISTER_OBJECT(Node, Sprite)
